#include "../include/Scene.h"
#include "../include/Object.h"
#include "../include/Sphere.h"
#include "../include/Triangle.h"
#include "../include/ModeleObj.h"
#include "../include/HitPoint.h"
#include "../include/pVect.h"
#include "../include/Camera.h"
#include "../include/Materiaux.h"
#include "../include/SceneReader.h"
#include "../include/Plan.h"
#include <list>
#include <iostream>
#include <cstdlib>
#include <math.h>


Scene::Scene()
{

    //ctor
}

Scene::~Scene()
{
    //dtor
}


/**
* Methode permettant la création et le rendu de l'image
* @param un ficher texte .scene contenant la scene voulu
* @param un ficher image .ppm contenant l'image de sortie
* @return une image
*/
Image* Scene::render(char* fichierE, char* fichierS){

    //lecture de la scene dans un fichier
    SceneReader sr(fichierE);

    //liste d'objets et de lumière de la scene
    listObject = sr.lObject;
    listLight = sr.lLight;

    //création de l'image de rendu et de la camera
    Image * rendu = new Image(sr.imghauteur,sr.imglargeur);
    Camera camera(sr.campos,sr.camdir,sr.focale,rendu);

    //choix de la couleur de fond
    this->fond = sr.fond;

    //nombre de rayon lancer en chaque point par l'ambiente oclusion
    this->aoRayon = sr.aoRayon;

    //gestion de l'anti-aliasing, nbPix est le nombre de rayon lancés par pixels
    int nbPix = pow(4,sr.antialiasing);

    //----parcours de l'image rendu----
    for(int j=0; j < rendu->height; j++){
        for(int i=0; i < rendu->width; i++){

            //couleur de départ du pixel principal
            Color bufColor = Color::BLACK;
            //creation du ou des rayons lancés par la caméra
            pVect* plrsrayon = camera.creerRayonAntiAliasing(i,j,sr.antialiasing);

            //pour chaque sous pixel on ajoute, au pixel principal, la couleur récupérée
            for(int u =0; u<nbPix; u++){
                Color pixelColor = this->lancer(camera.position, plrsrayon[u], sr.maxrecurs);
                bufColor.x = bufColor.x + pixelColor.x;
                bufColor.y = bufColor.y + pixelColor.y;
                bufColor.z = bufColor.z + pixelColor.z;
            }

            //moyenne de la couleur récupérée
            bufColor.x = bufColor.x/(nbPix);
            bufColor.y = bufColor.y/(nbPix);
            bufColor.z = bufColor.z/(nbPix);

            //coloriage du pixel principal
            rendu->setPixel(i,j, bufColor);
        }
        if(j%10==0) std::cout<<"Ligne "<<j<<" sur "<<rendu->height<<std::endl;
    }//----fin parcours de l'image rendu----

    //enregistrement de l'image dans un fichier
    rendu->exportToFile(fichierS);
    return rendu;
}

/**
* Methode permettant le lancer d'un rayon
* @param position point de départ du rayon
* @param rayon vecteur directeur du rayon
* @param recurs nombre de recursion encore possibles
* @param dontTest l'objet dont l'intersection rayon-objet n'est pas à tester
* @return la couleur de l'objet le plus proche intersecté par le rayon, ou noir
*/
Color Scene::lancer(pVect position, pVect rayon, int recurs, Object * dontTest){

    //si la recursion est encore possible
    if(recurs !=0){
        recurs--;   //une recursion utilisée

        //----boucle sur tous les objets de la scene----
        //Pour chaque objet, on teste s'il y a une intersection
        HitPoint* result = 0;
        HitPoint* buff;
        std::list<Object*>::iterator it;
        for(it = listObject.begin() ; it != listObject.end() ; it++){
            if((*it) != dontTest){
                buff=(*it)->intersect(position,rayon);

                //on conserve l'intersection rayon-objet la plus proche
                if(buff!=0){
                    if(result==0 || result->distance > buff->distance){
                        if(result != 0) delete result;
                        result = buff;
                    } else {
                        delete buff;
                    }
                }
            }
        }//----fin boucle sur tous les objets de la scene----

        //on renvoie la couleur de l'objet intersecté
        Color c = this->colorer(result,recurs);
        delete result;
        return c;

    }else{  //si la recursion n'est plus possible
        return Color::BLACK;    //renvoie la couleure noire
    }
}


/**
* Methode permettant de récupérer la couleur de l'objet intersecté
* @param hp point de l'intersection rayon-objet
* @param recurs nombre de recursion encore possibles
* @return la couleur de l'objet intersecté, ou noir s'il rien n'est intersecté (hp=0)
*/
Color Scene::colorer(HitPoint* hp, int recurs){

    //s'il n'y a pas d'intersection rayon-objet, on renvoie noir
    if(hp==0){
        return fond;
    }

    //définition de la couleur de base grace a l'environnement
    Color baseColor = Color::BLACK;
    if(this->aoRayon != 0){
        baseColor = this->ambientOcclusion(hp);
    }

    //calcul du rayon réfléchit du rayon caméra->point d'intersection
    pVect miroirCamera = hp->position - camera.position;
    miroirCamera.normalize();
    miroirCamera = miroirCamera - (hp->normale * 2 * (miroirCamera*hp->normale));
    miroirCamera.normalize();

    //Si l'objet a un matériaux miroir
    if(hp->objectTouched->materiaux.miroir > 0){
        //on lance un rayon réfléchit du rayon caméra->point d'intersection
        //on récupère la couleur de l'objet intersecté le plus proche
        Color reflect = this->lancer(hp->position,miroirCamera,recurs,hp->objectTouched);

        //on ne renvoie qu'une partie de la lumière réfléchit
        // si le miroir est "parfait" on renvoie toute la lumière
        reflect *= hp->objectTouched->materiaux.miroir;

        //on colorie le miroir
        baseColor += reflect;
    }

    //----Parcours de toutes les lumière de la scene----
    std::list<Light*>::iterator tor;
    for(tor = listLight.begin(); tor != listLight.end(); tor++){

        Light * l = (*tor);

        float puissanceRecue = l->getPuissanceAtPoint(hp,this);


        pVect vLumHP = hp->position - l->position;
        float dLumHP = vLumHP.norm();
        vLumHP.normalize();
        pVect vObjLum = l->position - hp->position;
        vObjLum.normalize();

        if(puissanceRecue > 0){
            //calcul de l'angle de spécularitué de la tache
            float angleMiroir = vObjLum*miroirCamera;
            angleMiroir = pow(angleMiroir,hp->objectTouched->materiaux.tache);
            Color obj;
            //calcul de la composante diffuse
            float cdif = 1.0/(M_PI * dLumHP*dLumHP)* hp->objectTouched->materiaux.coefDiff;
            //calcul de la composante spéculaire
            float cspec = ((hp->objectTouched->materiaux.tache+2)/(2*M_PI*dLumHP*dLumHP))*hp->objectTouched->materiaux.coefSpec*angleMiroir;

            //on donne la couleur calculée à l'objet
            // on multiplie la composante diffuse par la pigmentation de l'objet
            // on multiplie la composante speculaire par la couleur de la lumière
            // on somme les deux composantes et l'on multiplie le tout par scal (voir plus haut) et par la puissance de la lumière
            obj.x = (cdif*hp->objectTouched->materiaux.pigmentation.x*l->couleur.x + cspec*l->couleur.x)* puissanceRecue;
            obj.y = (cdif*hp->objectTouched->materiaux.pigmentation.y*l->couleur.y + cspec*l->couleur.y)* puissanceRecue;
            obj.z = (cdif*hp->objectTouched->materiaux.pigmentation.z*l->couleur.z + cspec*l->couleur.z)* puissanceRecue;

            //on cumule toutes les couleurs calculées pour un meme point
            baseColor.x = baseColor.x + obj.x;
            baseColor.y = baseColor.y + obj.y;
            baseColor.z = baseColor.z + obj.z;
        }

    }//----fin parcours de toutes les lumière de la scene----

    //si l'objet n'a pas été éclairé, il garde sa couleur initiale c'est a dire noir.
    return baseColor;
}

/**
* Methode permettant le calcul de la couleur ambiante en fonction de la couleur de fond
* @param hp point de l'intersection rayon-objet
* @return une couleur
*/
Color Scene::ambientOcclusion(HitPoint* hp){

    // nb de rayons lancés
    int nbRayonLance = this->aoRayon;

    //calcul du repere local
    pVect vB;
    pVect vA;
    pVect vC= hp->normale;
    vC.repere(vA, vB);

    Color sommeColor=Color::BLACK; //Somme des couleurs renvoyé par l'environnement

    //-----Pour chaque rayons relancer à partir du point d'impact
    for(int i=0; i<nbRayonLance; i++){
        //création de 2 variables aléatoires
        float r1 = (float)rand() / (float)RAND_MAX;
        float r2 = (float)rand() / (float)RAND_MAX;

        //calcul du vecteur rayon relancé relatif au repere local
        float x = cos(2*M_PI*r1)*sqrt(1-r2*r2);
        float y = sin(2*M_PI*r1)*sqrt(1-r2*r2);
        float z = r2;

        //calcul du rayon a relancé
        pVect rayon((vA.x*x + vB.x*y + vC.x*z),(vA.y*x + vB.y*y + vC.y*z),(vA.z*x + vB.z*y + vC.z*z));
        rayon.normalize();

        //Pour chaque objet de la scene, on regarde s'il intersecte le rayon objet->environnement
        HitPoint* buff;
        std::list<Object*>::iterator it;
        bool occulte=false;
        it = listObject.begin();
        while(!occulte && it != listObject.end()){
            buff=(*it)->intersect(hp->position,rayon);

            //si le rayon ne voit pas l'environnement
            if(buff!=0 && (*it)!=hp->objectTouched){
                occulte = true;

            }
            if(buff!=0) delete buff;
            it++;
        }
        //incrémentation du compteur de rayon visible
        if(!occulte){
            sommeColor+= fond*(vC*rayon);
        }
    }//-----Fin de la boucle sur les rayons

    //moyenne de la couleur
    sommeColor *= (2*M_PI)/nbRayonLance;
    sommeColor.x *= hp->objectTouched->materiaux.pigmentation.x;
    sommeColor.y *= hp->objectTouched->materiaux.pigmentation.y;
    sommeColor.z *= hp->objectTouched->materiaux.pigmentation.z;

    sommeColor /= 4;
    return sommeColor;

}

